#ifndef PROTOCOL_HPP
#define PROTOCOL_HPP
#include <vector>
#include <functional>
#include <string>
#include <cstdint>
#include <mutex>
#include <algorithm>
#include <stdexcept>

using namespace std;
using namespace std::placeholders;
using ByteArray = vector<uint8_t>;

class Protocol {
public:
    enum PackageType {
        TYPE_HANDSHAKE = 1,
        TYPE_HANDSHAKE_ACK = 2,
        TYPE_HEARTBEAT = 3,
        TYPE_DATA = 4,
        TYPE_KICK = 5
    };

    enum MessageType {
        TYPE_REQUEST = 0,
        TYPE_NOTIFY = 1,
        TYPE_RESPONSE = 2,
        TYPE_PUSH = 3
    };

    struct Package {
        PackageType type;
        ByteArray body;
    };

    struct Message {
        uint32_t id;
        MessageType type;
        bool compressRoute;
        string route;
        ByteArray body;
    };

    static const int PKG_HEAD_BYTES = 4;
    static const int MSG_FLAG_BYTES = 1;
    static const int MSG_ROUTE_CODE_BYTES = 2;
    static const int MSG_ID_MAX_BYTES = 5;
    static const int MSG_ROUTE_LEN_BYTES = 1;
    static const int MSG_ROUTE_CODE_MAX = 0xffff;

    static ByteArray strEncode(const string& str) {
        ByteArray result;
        for (char c : str) {
            uint16_t charCode = static_cast<uint16_t>(static_cast<uint8_t>(c));
            if (charCode <= 0x7f) {
                result.push_back(static_cast<uint8_t>(charCode));
            }
            else if (charCode <= 0x7ff) {
                result.push_back(static_cast<uint8_t>(0xc0 | (charCode >> 6)));
                result.push_back(static_cast<uint8_t>(0x80 | (charCode & 0x3f)));
            }
            else {
                result.push_back(static_cast<uint8_t>(0xe0 | (charCode >> 12)));
                result.push_back(static_cast<uint8_t>(0x80 | ((charCode & 0xfc0) >> 6)));
                result.push_back(static_cast<uint8_t>(0x80 | (charCode & 0x3f)));
            }
        }
        return result;
    }

    static string strDecode(const ByteArray& buffer) {
        string result;
        size_t offset = 0;
        while (offset < buffer.size()) {
            uint32_t charCode;
            if (buffer[offset] < 128) {
                charCode = buffer[offset++];
            }
            else if (buffer[offset] < 224) {
                charCode = ((buffer[offset] & 0x3f) << 6) + (buffer[offset + 1] & 0x3f);
                offset += 2;
            }
            else {
                charCode = ((buffer[offset] & 0x0f) << 12) +
                    ((buffer[offset + 1] & 0x3f) << 6) +
                    (buffer[offset + 2] & 0x3f);
                offset += 3;
            }
            result += static_cast<char>(charCode);
        }
        return result;
    }

    static ByteArray packageEncode(PackageType type, const ByteArray& body) {
        ByteArray buffer(PKG_HEAD_BYTES + body.size());
        size_t index = 0;

        buffer[index++] = static_cast<uint8_t>(type & 0xff);
        buffer[index++] = static_cast<uint8_t>((body.size() >> 16) & 0xff);
        buffer[index++] = static_cast<uint8_t>((body.size() >> 8) & 0xff);
        buffer[index++] = static_cast<uint8_t>(body.size() & 0xff);

        copy(body.begin(), body.end(), buffer.begin() + index);
        return buffer;
    }

    static vector<Package> packageDecode(const ByteArray& buffer) {
        vector<Package> result;
        size_t offset = 0;

        while (offset < buffer.size()) {
            Package pkg;
            pkg.type = static_cast<PackageType>(buffer[offset++]);

            size_t length = ((static_cast<size_t>(buffer[offset++]) << 16) |
                (static_cast<size_t>(buffer[offset++]) << 8) |
                static_cast<size_t>(buffer[offset++])) & 0xFFFFFFFF;

            if (length > 0 && offset + length <= buffer.size()) {
                pkg.body = ByteArray(buffer.begin() + offset, buffer.begin() + offset + length);
                offset += length;
            }
            else {
                pkg.body = ByteArray();
                offset = buffer.size();
            }

            result.push_back(pkg);
        }

        return result;
    }

    static ByteArray messageEncode(uint32_t id, MessageType type, bool compressRoute,
        const string& route, const ByteArray& msg) {
        int idBytes = msgHasId(type) ? calculateMsgIdBytes(id) : 0;
        size_t msgLen = MSG_FLAG_BYTES + idBytes;

        if (msgHasRoute(type)) {
            if (compressRoute) {
                msgLen += MSG_ROUTE_CODE_BYTES;
            }
            else {
                msgLen += MSG_ROUTE_LEN_BYTES + route.size();
            }
        }

        if (!msg.empty()) {
            msgLen += msg.size();
        }

        ByteArray buffer(msgLen);
        size_t offset = 0;


        offset = encodeMsgFlag(type, compressRoute, buffer, offset);


        if (msgHasId(type)) {
            offset = encodeMsgId(id, buffer, offset);
        }


        if (msgHasRoute(type)) {
            offset = encodeMsgRoute(compressRoute, route, buffer, offset);
        }


        if (!msg.empty()) {
            offset = encodeMsgBody(msg, buffer, offset);
        }

        return buffer;
    }

    static Message messageDecode(const ByteArray& buffer) {
        Message msg;
        size_t offset = 0;

        if (buffer.empty()) {
            return msg;
        }


        uint8_t flag = buffer[offset++];
        msg.compressRoute = (flag & 0x1) != 0;
        msg.type = static_cast<MessageType>((flag >> 1) & 0x7);


        if (msgHasId(msg.type)) {
            msg.id = 0;
            int i = 0;
            uint8_t m;
            do {
                m = buffer[offset++];
                msg.id += (static_cast<uint32_t>(m & 0x7f) << (7 * i));
                i++;
            } while (m >= 128 && offset < buffer.size());
        }
        else {
            msg.id = 0;
        }


        if (msgHasRoute(msg.type)) {
            if (msg.compressRoute) {

                if (offset + 1 < buffer.size()) {
                    uint16_t routeCode = (static_cast<uint16_t>(buffer[offset++]) << 8) | buffer[offset++];
                    msg.route = to_string(routeCode);
                }
            }
            else {
                if (offset < buffer.size()) {
                    uint8_t routeLen = buffer[offset++];
                    if (routeLen > 0 && offset + routeLen <= buffer.size()) {
                        ByteArray routeBytes(buffer.begin() + offset, buffer.begin() + offset + routeLen);
                        msg.route = strDecode(routeBytes);
                        offset += routeLen;
                    }
                }
            }
        }


        if (offset < buffer.size()) {
            msg.body = ByteArray(buffer.begin() + offset, buffer.end());
        }

        return msg;
    }

private:
    static bool msgHasId(MessageType type) {
        return type == TYPE_REQUEST || type == TYPE_RESPONSE;
    }

    static bool msgHasRoute(MessageType type) {
        return type == TYPE_REQUEST || type == TYPE_NOTIFY || type == TYPE_PUSH;
    }

    static int calculateMsgIdBytes(uint32_t id) {
        int len = 0;
        do {
            len++;
            id >>= 7;
        } while (id > 0);
        return len;
    }

    static size_t encodeMsgFlag(MessageType type, bool compressRoute, ByteArray& buffer, size_t offset) {
        buffer[offset] = static_cast<uint8_t>((type << 1) | (compressRoute ? 1 : 0));
        return offset + MSG_FLAG_BYTES;
    }

    static size_t encodeMsgId(uint32_t id, ByteArray& buffer, size_t offset) {
        do {
            uint8_t tmp = static_cast<uint8_t>(id % 128);
            uint32_t next = id / 128;

            if (next != 0) {
                tmp |= 0x80;
            }

            buffer[offset++] = tmp;
            id = next;
        } while (id != 0);

        return offset;
    }

    static size_t encodeMsgRoute(bool compressRoute, const string& route, ByteArray& buffer, size_t offset) {
        if (compressRoute) {

            uint16_t routeCode = 0;//!!
            buffer[offset++] = static_cast<uint8_t>((routeCode >> 8) & 0xff);
            buffer[offset++] = static_cast<uint8_t>(routeCode & 0xff);
        }
        else {
            ByteArray routeBytes = strEncode(route);
            if (routeBytes.size() > 255) {
                throw runtime_error("Route length exceeds maximum limit");
            }

            buffer[offset++] = static_cast<uint8_t>(routeBytes.size() & 0xff);
            copy(routeBytes.begin(), routeBytes.end(), buffer.begin() + offset);
            offset += routeBytes.size();
        }

        return offset;
    }

    static size_t encodeMsgBody(const ByteArray& msg, ByteArray& buffer, size_t offset) {
        copy(msg.begin(), msg.end(), buffer.begin() + offset);
        return offset + msg.size();
    }
};

#endif